from copy import deepcopy
import pandas as pd

class XlsxWriterFormat():
    '''此类用于实现对pandas产生的DataFrame数据进行格式化处理，以输出美观的excel文件。
    path：要生成xlsx文件的路径。
    '''
    TITLE_DEFAULT_FORMAT = {
        'font_name':'方正小标宋简体',
        'font_size':22,
        'font_color':'#000000',
        'text_wrap':True,
        #'bold':True,
        #'bg_color':'#FF0000',
        #'fg_color':'#00FF00',
        'align':'center',
        'valign':'vcenter',
        'border':1,
        'top':1,
        'left':1,
        'right':1,
        'bottom':1
    }
    
    COLUMNS_DEFAULT_FORMAT = {
        'font_name':'仿宋',
        'font_size':11,
        'font_color':'#000000',
        'text_wrap':True,
        'bold':True,
        #'bg_color':'#FF0000',
        #'fg_color':'#00FF00',
        'align':'center',
        'valign':'vcenter',
        'border':1,
        'top':1,
        'left':1,
        'right':1,
        'bottom':1
    }
    
    DATA_DEFAULT_FORMAT = {
        'font_name':'仿宋',
        'font_size':11,
        'font_color':'#000000',
        'text_wrap':True,
        'bold':False,
        #'bg_color':'#FF0000',
        #'fg_color':'#00FF00',
        'align':'center',
        'valign':'vcenter',
        'border':1,
        'top':1,
        'left':1,
        'right':1,
        'bottom':1
    }
    
    def __init__(self,path):
        self._path = path           #要生成xlsx文件的路径
        self._writer = None         #xlsxwriter对象
        
    def __enter__(self):            #支持使用with ... as ...语法
        return self
        
    def __exit__(self,type,value,trace):     #支持使用with ... as ...语法
        self.close()
        
    def _getWorkBook(self):         
        '''获取要操作的xlsxwriter对象
        '''
        self._writer = pd.ExcelWriter(self._path,engine="xlsxwriter")
        self._workbook = self._writer.book 
        
        
    def _setWorkBookStyle(self,columns,data_style,columns_style,title_style):
        '''添加工作簿中用到的格式
        style:表示数据部分的样式
        columns_style:表示列标题的样式
        title_style：表示标题的样式
        '''
        #实现方法为：复制出默认的样式，然后使用给出的样式去进行更新
        #print("setp 1:set title style obj")
        title_styel_dict = deepcopy(XlsxWriterFormat.TITLE_DEFAULT_FORMAT)
        #print("after deepcopy")
        title_styel_dict.update(title_style)
        #print("add format")
        self._title_style = self._workbook.add_format(title_styel_dict)
        
        #标题和数据部分样式需要按单元格处理
        #print("setp 2:set columns style obj")
        self._columns_style = {}
        for item in columns:
            columns_style_dict = deepcopy(XlsxWriterFormat.COLUMNS_DEFAULT_FORMAT)
            if item in columns_style:
                user_columns_style = columns_style[item]
                columns_style_dict.update(user_columns_style)
            self._columns_style[item] = self._workbook.add_format(columns_style_dict)
        
        #print("setp 3:set data style obj")
        self._data_style = {}
        for item in columns:
            data_style_dict = deepcopy(XlsxWriterFormat.DATA_DEFAULT_FORMAT)
            if item in data_style:
                user_data_style = data_style[item]
                data_style_dict.update(user_data_style)
            self._data_style[item] = self._workbook.add_format(data_style_dict)
        #print("end add format")
        
    def _foramtTitle(self,sheet,title,max_col):
        '''格式化输出标题
        '''
        sheet.merge_range(0,0,0,max_col-1,title,self._title_style)
        #设置行高
        sheet.set_row(0,60)   #设置第一行的行高为60
        
    def _formatColumns(self,sheet,columns,colunmn_width,data_start_row):
        '''格式化输出列标题
        '''
        for col_index,item in enumerate(columns):
            if item in colunmn_width:
                sheet.set_column(col_index,col_index,colunmn_width[item])
            sheet.write(data_start_row,col_index,item,self._columns_style[item])
            
    def _formatDatas(self,df,sheet,colunmn_width,data_start_row):
        '''格式化输出数据内容
        '''
        columns = df.columns.values
        row_index = data_start_row
        for index,line in df.iterrows():
            for col_index,item in enumerate(columns):
                if item in colunmn_width:
                    sheet.set_column(col_index,col_index,colunmn_width[item])
                value = line[item]
                if pd.isna(value):
                    value = ''
                sheet.write(row_index,col_index,value,self._data_style[item])
            row_index += 1
            
    def detectColumnMaxWidth(self,df,colunmn_width):
        '''根据df中的DataFrame数据计算每列的字符最长值，从而自动确定列宽
        df：要输出的DataFrame数据。默认df为标准格式的数据，首行为标题，下面全部为数据。
        '''
        columns = df.columns.values
        
        columns_max_len = {}
        for item in columns:
            columns_max_len[item] = max(df[item].astype('str').str.len())+4
            
        columns_max_len.update(colunmn_width)
        return columns_max_len
        
    def to_excel(self,df,sheetname,title=None,colunmn_width={},data_style={},columns_style={},title_style={},index=False):
        '''对df中的DataFrame输出进行格式化美化。
        df：要输出的DataFrame数据。默认df为标准格式的数据，首行为标题，下面全部为数据。
        title:输出数据的标题，若为空，则表示无标题。
        index:是否输出df中的index列，默认不输出。
        '''
        if not self._writer:
            #使用时获取workbook对象，全局只获取一次
            self._getWorkBook()
        try:
            #有可能对同一个Sheet进行操作，则直接覆盖
            self._worksheet = self._workbook.add_worksheet(sheetname)
        except DuplicateWorksheetName:
            self._worksheet = self._writer.sheets[sheetname] 
            
        #首先，向工作簿添加此工作表中用到的所有样式
        columns = df.columns.values
        self._setWorkBookStyle(columns,data_style,columns_style,title_style)
            
        #从现在开始进行数据及格式处理
        #总体思路为：
        # 1.先对标题进行处理
        max_row,max_col = df.shape
        data_start_row = 0
        #print("Set title style")
        if title:
            #处理标题
            #标题只有一行，为合并单元格
            self._foramtTitle(self._worksheet,title,max_col)
            data_start_row = 1
            
        # 2.对列标题进行处理
        # 通过算法计算每列最长值，确定列宽。此列宽对标题列和数据列同时有效
        colunmn_width = self.detectColumnMaxWidth(df,colunmn_width)
        self._formatColumns(self._worksheet,columns,colunmn_width,data_start_row)
        data_start_row += 1
        
        # 3.对数据部分按单元格进行处理
        self._formatDatas(df,self._worksheet,colunmn_width,data_start_row)
        # 可见，处理速度较慢，需要权衡
        
    def close(self):
        '''关闭xlsxwriter对象，关闭时对所进行的文件操作进行保存。
        '''
        self._writer.close()
        self._writer = None
    